﻿using DoNet.Common.Cache;
using DoNet.Common.Encrypt;
using DoNet.Common.Extensions;
using DoNet.Common.Helpers;
using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using MySql.Data.MySqlClient;
using Npgsql;
using Oracle.ManagedDataAccess.Client;
using System.Data;
using System.Reflection;
using System.Runtime.InteropServices;

namespace DoNet.Core.DataManager
{
    /// <summary>
    /// 数据库服务提供者
    /// </summary>
    public class DbServerProvider
    {
        /// <summary>
        /// 数据库连接名称
        /// </summary>
        public string dbConfigName = string.Empty;
        /// <summary>
        /// 数据库字符串是否加密
        /// </summary>
        public bool isConnectionStringEncrypt = false;
        /// <summary>
        /// 开启数据库读写分离
        /// </summary>
        public bool isDbReadWriteSeparate = false;
        /// <summary>
        /// 配置文件中的所有数据库连接
        /// </summary>
        public List<DbConnections> allDbConnections = new List<DbConnections>();
        /// <summary>
        /// 多个从数据库的访问策略
        /// </summary>
        public string queryDbStrategy = string.Empty;
        /// <summary>
        /// 
        /// </summary>
        public bool sqlAopEnabled = false;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="config"></param>
        public DbServerProvider(IConfiguration config)
        {
            isConnectionStringEncrypt = config.app("ConnectionStringEncrypt").ObjectToBool();
            isDbReadWriteSeparate = config.app("IsDbReadWriteSeparate").ObjectToBool();
            if (string.IsNullOrWhiteSpace(dbConfigName))
            {
                dbConfigName = config.app("DefaultDataBase");
            }
            allDbConnections = config.app<DbConnections>("DbConnections").Where(i => i.Enabled).ToList();
            queryDbStrategy = config.app("QueryDbStrategy");
            sqlAopEnabled = config.app("SqlAOP", "Enabled").ObjectToBool();
        }

        #region Dapper Context

        /// <summary>
        /// 获取默认数据库连接
        /// </summary>
        /// <returns></returns>
        public string GetConnectionString()
        {
            return GetConnectionString(dbConfigName);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string GetConnectionString(string key)
        {
            return dbConfigName = key ?? dbConfigName;
        }

        /// <summary>
        /// 获取数据库连接
        /// </summary>
        /// <param name="masterDb">是否访问主库，默认为是，否则访问从库即只读数据库</param>
        /// <returns></returns>
        public IDbConnection? GetDBConnection<TEntity>(bool masterDb = true)
        {
            var connectionOptions = GeDbConnectionOptions<TEntity>(masterDb);
            if (connectionOptions != null)
            {
                string defaultSqlConnectionString = connectionOptions.ConnectionString;
                DatabaseType dbType = connectionOptions.DatabaseType;
                return GetDbConnection(dbType, defaultSqlConnectionString);
            }
            return default;
        }

        /// <summary>
        /// 获取数据库连接
        /// </summary>
        /// <param name="masterDb">是否访问主库，默认为是，否则访问从库即只读数据库</param>
        /// <returns></returns>
        public IDbConnection? GetDBConnection(bool masterDb = true)
        {
            var connectionOptions = GeDbConnectionOptions(masterDb);
            if (connectionOptions != null)
            {
                string defaultSqlConnectionString = connectionOptions.ConnectionString;
                DatabaseType dbType = connectionOptions.DatabaseType;
                return GetDbConnection(dbType, defaultSqlConnectionString);
            }
            return default;
        }

        /// <summary>
        /// 根据数据库类型及连接字符串获取数据库连接
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="connectionString"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        public static IDbConnection GetDbConnection(DatabaseType dbType, string connectionString)
        {
            switch (dbType)
            {
                case DatabaseType.SqlServer:
                    return new SqlConnection(connectionString);
                case DatabaseType.MySql:
                    return new MySqlConnection(connectionString);
                case DatabaseType.Oracle:
                    return new OracleConnection(connectionString);
                case DatabaseType.SQLite:
                    return new SqliteConnection(connectionString);
                case DatabaseType.Npgsql:
                    return new NpgsqlConnection(connectionString);
                default:
                    throw new NotSupportedException("The database is not supported");
            }
        }

        /// <summary>
        /// 获取数据库连接连接配置
        /// </summary>
        /// <typeparam name="TEntity">数据实体</typeparam>
        /// <param name="masterDb">是否访问主库，默认为是，否则访问从库即只读数据库</param>
        /// <returns></returns>
        public DbConnectionOptions? GeDbConnectionOptions<TEntity>(bool masterDb = true)
        {
            var attrDbConfigName = typeof(TEntity).GetCustomAttribute<AppDbContextAttribute>(false)?.DbConfigName;
            if (!string.IsNullOrWhiteSpace(attrDbConfigName))
            {
                dbConfigName = attrDbConfigName;
            }
            return GeDbConnectionOptions(masterDb);
        }

        /// <summary>
        /// 获取数据库连接连接配置
        /// </summary>
        /// <param name="masterDb">是否访问主库，默认为是，否则访问从库即只读数据库</param>
        /// <returns></returns>
        public DbConnectionOptions? GeDbConnectionOptions(bool masterDb = true)
        {
            DbConnectionOptions? dbConnectionOptions = null;
            var dbConnections = allDbConnections.Where(m => m.ConnId == dbConfigName).FirstOrDefault();
            if (dbConnections != null)
            {
                if (masterDb || !isDbReadWriteSeparate)
                {
                    dbConnectionOptions = new DbConnectionOptions();
                    dbConnectionOptions.ConnectionString = dbConnections.MasterDB.ConnectionString;
                    dbConnectionOptions.DatabaseType = dbConnections.MasterDB.DatabaseType;
                }
                else
                {
                    dbConnectionOptions = GetReadConn(dbConnections.ReadDB);
                }
                if (dbConnectionOptions != null)
                {
                    if (isConnectionStringEncrypt)
                    {
                        dbConnectionOptions.ConnectionString = DEncrypt.Decrypt(dbConnectionOptions.ConnectionString);
                    }
                }
            }
            return dbConnectionOptions;
        }

        /// <summary>
        /// 按从库数据库连接的策略进行返回连接对象，实现从库的负载均衡
        /// </summary>
        /// <param name="slaveData"></param>
        /// <returns></returns>
        private DbConnectionOptions? GetReadConn(List<DbConnectionOptions> slaveData)
        {
            string[] ids = { "Server1", "Server2", "Server3", "Server4" };
            int[] weights = slaveData.Select(x=>x.HitRate).ToArray();

            Span<DbConnectionOptions> idSpan = CollectionsMarshal.AsSpan(slaveData);
            Span<int> weightSpan = weights.AsSpan();
            var selector = new WeightedPloy<DbConnectionOptions>(idSpan, weightSpan);
            switch (queryDbStrategy)
            {
                case "Random": //随机策略
                    return selector.GetNextRandom();
                case "Polling": //轮询策略
                    return selector.GetNextPolling();
                case "Weight": //权重策略
                    return selector.GetNextWeightPolling();
            }
            return default;
        }

        #endregion
    }
}
